package com.example.service.user.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.domain.user.User;
import com.example.domain.user.dto.UserDTO;
import com.example.domain.user.dto.UserUpdateDTO;
import com.example.domain.user.vo.UserVO;
import com.example.manager.UserCacheManager;
import com.example.mapper.user.UserMapper;
import com.example.redis.service.RedisService;
import com.example.security.exception.ServiceImplException;
import com.example.security.service.TokenService;
import com.example.service.AliSmsService;
import com.example.service.user.IUserService;
import io.jsonwebtoken.Claims;
import oj.common.core.contants.CacheConstants;
import oj.common.core.contants.Constants;
import oj.common.core.contants.HttpConstants;
import oj.common.core.contants.JwtConstants;
import oj.common.core.domain.LoginUser;
import oj.common.core.domain.LoginUserVO;
import oj.common.core.domain.Result;
import oj.common.core.enums.ResultCode;
import oj.common.core.enums.UserIdentity;
import oj.common.core.enums.UserStatus;
import oj.common.core.utils.JwtUtils;
import oj.common.core.utils.ThreadLocalUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

@Service
public class UserServiceImpl implements IUserService {
    private static final Pattern PHONE_PATTERN =
            Pattern.compile("^1[3-9]\\d{9}$");
    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
    @Autowired
    private AliSmsService aliSmsService;
    @Autowired
    private UserCacheManager userCacheManager;
    @Autowired
    private RedisService redisService;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private TokenService tokenService;
    @Value("${sms.code-expiration: 5}")
    private Integer phoneCodeExpiration;
    @Value("${sms.send-limit: 3}")
    private Integer sendLimit;
    @Value("${sms.is-send: false}")
    private boolean isSend;  //开关打开：true  开关关闭false

    @Value("${jwt.secret}")
    private String secret;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;


    @Override
    public boolean sendCode(UserDTO userDTO) {
        if(!isValidPhone(userDTO.getPhone())) {
            throw new ServiceImplException(ResultCode.FAILED_USER_PHONE);
        }
        String phoneCodeKey = getPhoneCodeKey(userDTO.getPhone());
        //不允许在一分钟之内重复发送验证码
        Long expire = redisService.getExpire(phoneCodeKey, TimeUnit.SECONDS);
        if (expire != null && (phoneCodeExpiration* 60 - expire) < 60 ){
            throw new ServiceImplException(ResultCode.FAILED_FREQUENT);
        }
        String codeTimeKey = getCacheCodeKey(userDTO.getPhone());
        Long times = redisService.getCacheObject(codeTimeKey, Long.class);
        //这是拿到增加的次数，然后给阈值进行比较。
        if(times!=null && times>sendLimit) {
            throw new ServiceImplException(ResultCode.FAILED_FREQUENT);
        }
        //这时候开始向redis中存储数据
        String code = isSend ? RandomUtil.randomNumbers(6) : Constants.DEFAULT_CODE;
        //这里应该设置的一个五分钟过期时间。
        redisService.setCacheObject(phoneCodeKey,code,5L,TimeUnit.MINUTES);
        //这里发送短信
        if(isSend) {
            boolean sendMobileCode = aliSmsService.sendMobileCode(userDTO.getPhone(), code);
            if(!sendMobileCode) {
                throw new ServiceImplException(ResultCode.FAILED_SEND_CODE);
            }
        }
        //这里判断由于是不是第一次设置缓存，效果是计数器第二天到期
        redisService.increment(codeTimeKey);
        if(times==null) {
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeTimeKey, seconds, TimeUnit.SECONDS);
        }
        return true;
    }

    @Override
    //要判断是不是新的用户，如果是新的用走的是注册+登录功能
    //不是新的用户的话，直接走的是登录功能。
    //所以说，两步共同的任务都有验证码进行验证。
    public String login(String phone,String code) {
        checkCode(phone, code);
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
                .eq(User::getPhone, phone));
        if (user == null) {
            user = new User();
            user.setStatus(UserStatus.Normal.getValue());
            user.setPhone(phone);
            user.setCode(code);
            userMapper.insert(user);
        }
        return tokenService.createToken(user.getUserId(), UserIdentity.ORDINARY.getValue(), secret,user.getNickName(),user.getHeadImage());
    }

    @Override
    public boolean logout(String token) {
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        Claims claims = JwtUtils.parseToken(token, secret);
        String userKey=tokenService.getUserKey(claims);
        String tokenKey=tokenService.getTokenKey(userKey);
        return  redisService.deleteObject(tokenKey);
    }

    @Override
    public Result<LoginUserVO> info(String token) {
        //对token进行处理
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        LoginUser loginUser = tokenService.getLoginUser(token, secret);
        if(loginUser==null) {
            return Result.failed();
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickName(loginUser.getNickName());
        //这里还得进行判空处理，因为在登录的时候不一定要传入一个头像文件。
        loginUserVO.setHeadImage(downloadUrl + loginUser.getHeadImage());
        return Result.success(loginUserVO);
    }

    @Override
    public int edit(UserUpdateDTO userUpdateDTO) {
        Long userId = ThreadLocalUtil.get(JwtConstants.LOGIN_USER_ID,Long.class);
        if(userId==null) {
            throw new ServiceImplException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if(user==null) {
            throw new ServiceImplException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        user.setNickName(userUpdateDTO.getNickName());
        user.setSex(userUpdateDTO.getSex());
        user.setSchoolName(userUpdateDTO.getSchoolName());
        user.setMajorName(userUpdateDTO.getMajorName());
        user.setPhone(userUpdateDTO.getPhone());
        user.setEmail(userUpdateDTO.getEmail());
        user.setWechat(userUpdateDTO.getWechat());
        user.setIntroduce(userUpdateDTO.getIntroduce());
        //这里还要进项刷新缓存当中LoginUser
        userCacheManager.refreshUser(user);
        //这里应该要拿到这个，返回的结果来进行封装这个路径
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtil.get(JwtConstants.LOGIN_USER_KEY, String.class));
        return userMapper.updateById(user);
    }

    @Override
    public UserVO detail() {
        Long userId = ThreadLocalUtil.get(JwtConstants.LOGIN_USER_ID,Long.class);
        if(userId==null) {
            throw new ServiceImplException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if(user==null) {
            throw new ServiceImplException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        UserVO userVO = userCacheManager.getUserVOById(userId);
        if (userVO == null) {
            throw new ServiceImplException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        //因为在展示的时候，如果返回前端的数据，数据库只是将文件的名字给进行存储，然后再去数据拿文件名，然后将数据库进行拼接文件路径。
        if (StrUtil.isNotEmpty(userVO.getHeadImage())) {
            userVO.setHeadImage(downloadUrl + userVO.getHeadImage());
        }
        return userVO;
    }

    @Override
    public int updateImage(UserUpdateDTO userUpdateDTO) {
        Long userId = ThreadLocalUtil.get(JwtConstants.LOGIN_USER_ID,Long.class);
        if(userId==null) {
            throw new ServiceImplException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if(user==null) {
            throw new ServiceImplException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        user.setHeadImage(userUpdateDTO.getHeadImage());
        userCacheManager.refreshUser(user);
        //这里应该要拿到这个，返回的结果来进行封装这个路径
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtil.get(JwtConstants.LOGIN_USER_KEY, String.class));
        return userMapper.updateById(user);
    }

    private void checkCode(String phone,String code) {
        String phoneCodeKey = getPhoneCodeKey(phone);
        log.info("key:{}",phoneCodeKey);
        String cacheCOde = redisService.getCacheObject(phoneCodeKey, String.class);
        log.info("验证码:"+cacheCOde);
        if(StrUtil.isEmpty(cacheCOde)) {
            throw new ServiceImplException(ResultCode.FAILED_INVALID_CODE);
        }
        if(!code.equals(cacheCOde)) {
            throw new ServiceImplException(ResultCode.FAILED_ERROR_CODE);
        }
        redisService.deleteObject(phoneCodeKey);
    }

    private String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY+phone;
    }
    private String getCacheCodeKey(String phone) {
        return CacheConstants.CODE_TIME_KEY+phone;
    }
    public boolean isValidPhone(String phone) {
        return PHONE_PATTERN.matcher(phone).matches();
    }
}
